package com.swak.fx.support;

import java.awt.SystemTray;
import java.awt.image.BufferedImage;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.eventbus.EventBus;
import com.swak.config.setting.Settings;
import com.swak.exception.InvokeException;
import com.swak.reactivex.threads.OrderContext.OrderAbleContext;
import com.swak.reactivex.threads.TimerContext;
import com.swak.reactivex.threads.WorkerContext;
import com.swak.ui.Event;
import com.swak.ui.EventListener;
import com.swak.ui.Events;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.stage.Stage;
import lombok.Getter;

/**
 * AbstractApplication.
 * 
 * 执行顺序：
 * 1. 构造方法
 * 2. init
 * 3. start
 * 
 * @author 618lf
 */
public abstract class AbstractApplication extends Application implements EventListener {

	// 整个系统唯一的应用
	static AbstractApplication INSTANCE = null;

	// ####################
	// 需要开启此参数 -- 设置为false ： spring 中会设置为 true
	// ####################
	private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";

	// ####################
	// 启动应用
	// ####################
	private static Logger LOGGER = LoggerFactory.getLogger(AbstractApplication.class);
	private static String[] savedArgs = new String[0];
	private static Class<? extends Window> mainView;
	private static Class<? extends Window> splashView;

	public static void launch(final Class<? extends Application> appClass, final Class<? extends Window> mainClass,
			final Class<? extends Window> splashScreen, final String[] args) {
		splashView = splashScreen;
		launch(appClass, mainClass, args);
	}

	public static void launch(final Class<? extends Application> appClass, final Class<? extends Window> mainClass,
			final String[] args) {
		mainView = mainClass;
		savedArgs = args;
		doLaunch(appClass, args);
	}

	static void doLaunch(final Class<? extends Application> appClass, final String[] args) {
		System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, "false");
		Platform.setImplicitExit(false);
		Settings.intSettings();
		Application.launch(appClass, args);
	}

	// ####################
	// 应用配置
	// ####################
	private final CompletableFuture<Runnable> splashIsShowing;
	private final EventBus eventBus;
	@Getter
	private final WorkerContext eventbusContext;
	@Getter
	private final Store $store;
	@Getter
	private final OrderAbleContext $context;
	@Getter
	private final TimerContext $timer;
	private Tray tray;

	protected AbstractApplication() {
		this.$store = Store.New();
		this.$context = FxContexts.New().getWorkerContext().newOrderAbleContext();
		this.$timer = FxContexts.New().getTimerContext();
		this.eventbusContext = FxContexts.New().getEventContext();
		this.splashIsShowing = new CompletableFuture<>();
		this.eventBus = Events.New(this.eventbusContext);
		this.eventBus.register(this);
		AbstractApplication.INSTANCE = this;
	}

	protected CompletableFuture<Void> execute(Runnable run) {
		CompletableFuture<Void> future = new CompletableFuture<Void>();
		this.eventbusContext.execute(() -> {
			try {
				run.run();
			} catch (Exception e) {
				future.completeExceptionally(e);
			} finally {
				if (!future.isDone()) {
					future.complete(null);
				}
			}
		});
		return future;
	}

	/**
	 * 定制启动流程： 设置了 Splash 的启动动画时，需要在 Splash 中主动调用 doStart
	 */
	@Override
	public void init() throws Exception {
		if (splashView == null || (!Splash.class.isAssignableFrom(splashView))) {
			this.startApp();
		}
	}

	/**
	 * 实际上的启动服务
	 */
	void startApp() {
		this.doStart(savedArgs).whenComplete((ctx, throwable) -> {
			if (throwable != null) {
				LOGGER.error("Failed to load spring application context: ", throwable);
				Platform.runLater(() -> showErrorAlert(throwable));
			}
		}).thenAcceptBothAsync(splashIsShowing, (ctx, closeSplash) -> {
			closeSplash.run();
		});
	}

	/**
	 * 执行启动： 此时服务已经在启动过程中
	 */
	@Override
	public void start(final Stage stage) throws Exception {
		Display.setStage(stage);
		this.customStage(stage);
		if (splashView != null) {
			final Window splash = this.NewWindow(splashView, Window.createStage());
			splash.show();
			splashIsShowing.complete(() -> {
				splash.waitClose().whenComplete((v, t) -> {
					Display.runUI(() -> {
						showMainView().whenComplete((v1, t1) -> {
							Display.runUI(() -> {
								v1.run();
								splash.destory();
							});
						});
					});
				});
			});
		} else {
			splashIsShowing.complete(() -> {
				Display.runUI(() -> {
					showMainView().whenComplete((v1, t1) -> {
						Display.runUI(() -> {
							v1.run();
						});
					});
				});
			});
		}
	}

	/**
	 * 显示主界面
	 */
	protected CompletableFuture<Runnable> showMainView() {
		CompletableFuture<Runnable> mainViewInited = new CompletableFuture<>();
		try {
			final Window mainPage = this.NewWindow(mainView, Display.getStage());
			this.customTray();
			mainPage.whenInited().thenAcceptAsync(v -> {
				mainViewInited.complete(() -> {
					mainPage.show();
				});
			});
		} catch (Throwable t) {
			LOGGER.error("Failed to load application: ", t);
			mainViewInited.completeExceptionally(t);
			showErrorAlert(t);
		}
		return mainViewInited;
	}

	protected void showErrorAlert(Throwable throwable) {
		Dialogs.error(throwable).ifPresent(response -> this.listen(Event.EXIT));
	}

	private void customTray() throws Exception {
		Supplier<Window> trayWindow = this.customTrayWindow();
		if (trayWindow != null && SystemTray.isSupported()) {
			BufferedImage image = Snapshots.fromFXImage(Display.logo(), null);
			this.tray = new Tray(image, Display.getStage().getTitle());
			this.tray.setMenu(trayWindow);
			SystemTray.getSystemTray().add(tray);
		}
	}

	protected Supplier<Window> customTrayWindow() {
		return null;
	}

	/**
	 * 启动服务
	 * 
	 * @param args
	 */
	protected abstract CompletableFuture<Void> doStart(String[] args);

	/**
	 * 停止服务
	 * 
	 * @param savedArgs
	 */
	protected abstract CompletableFuture<Void> doStop();

	/**
	 * 加载 logo
	 * 
	 * @return
	 */
	protected abstract void customStage(final Stage stage);

	// ================================================================================
	// 事件处理
	// ================================================================================
	@Override
	public void listen(Event event) {
		if (event == Event.EXIT) {
			this.stopApp();
		}
	}

	// ================================================================================
	// 停止
	// ================================================================================

	void stopApp() {
		try {
			Platform.exit();
		} catch (Exception e) {
		}
	}

	@Override
	public void stop() throws Exception {
		super.stop();

		try {
			this.eventBus.unregister(this);
		} catch (Exception e) {
		}

		try {
			if (this.tray != null) {
				SystemTray.getSystemTray().remove(this.tray);
			}
		} catch (Exception e) {
		}

		try {
			this.windows.destory();
		} catch (Exception e) {
		}

		try {
			Settings.me().storeConfig();
		} catch (Exception e) {
		}

		this.doStop().whenComplete((v, t) -> {
			try {
				FxContexts.New().destory();
			} catch (Exception e) {
			}
		});
	}

	// ================================================================================
	// 窗口管理
	// ================================================================================
	private final Parent windows = new Parent();

	public Window NewWindow(Class<? extends Window> pageClass) {
		FXMLView view = pageClass.getAnnotation(FXMLView.class);
		try {
			return windows.NewPage(pageClass, view != null ? view.singleton() : false, null, null);
		} catch (Throwable e) {
			LOGGER.error("New Page {} Error,", pageClass.getName(), e);
			throw new InvokeException(e);
		}
	}

	public Window NewWindow(Class<? extends Window> pageClass, Stage stage) {
		FXMLView view = pageClass.getAnnotation(FXMLView.class);
		try {
			return windows.NewPage(pageClass, view != null ? view.singleton() : false, new Class[] { Stage.class },
					new Object[] { stage });
		} catch (Throwable e) {
			LOGGER.error("New Page {} Error,", pageClass.getName(), e);
			throw new InvokeException(e);
		}
	}
}